#include "dev_sy.h"

static void _hl_log(int ret, const char *format, ...)
{
    char *msg = "";
    switch (ret)
    {
    case HL_ERROR_SUCCESSFUL:
        msg = "HL_ERROR_SUCCESSFUL 成功操作";
        break;
    case HL_ERROR_NO_INITIALIZED:
        msg = "HL_ERROR_NO_INITIALIZED 库未初始化";
        break;
    case HL_ERROR_ALREADY_INITIALIZED:
        msg = "HL_ERROR_ALREADY_INITIALIZED 已经初始化好了，不需要再初始化";
        break;
    case HL_ERROR_TIME_OUT:
        msg = "HL_ERROR_TIME_OUT 操作超时，可以重复操作";
        break;
    case HL_ERROR_INVALID_ID:
        msg = "HL_ERROR_INVALID_ID 无效DID";
        break;
    case HL_ERROR_INVALID_PARAMETER:
        msg = "HL_ERROR_INVALID_PARAMETER 无效参数";
        break;
    case HL_ERROR_DEVICE_NOT_ONLINE:
        msg = "HL_ERROR_DEVICE_NOT_ONLINE 设备不在线";
        break;
    case HL_ERROR_FAIL_TO_RESOLVE_NAME:
        msg = "HL_ERROR_DNS_RESOLVE_FAIL 域名解析失败";
        break;
    case HL_ERROR_INVALID_PREFIX:
        msg = "HL_ERROR_INVALID_PREFIX 无效前缀";
        break;
    case HL_ERROR_ID_OUT_OF_DATE:
        msg = "HL_ERROR_ID_OUT_OF_DATE DID过期，被加入黑名单";
        break;
    case HL_ERROR_NO_RELAY_SERVER_AVAILABLE:
        msg = "HL_ERROR_NO_RELAY_SERVER_AVAILABLE 没有可用的转发服务器";
        break;
    case HL_ERROR_INVALID_SESSION_HANDLE:
        msg = "HL_ERROR_SESSION_INVALID_HANDLE session无效";
        break;
    case HL_ERROR_SESSION_CLOSE_REMOTE:
        msg = "HL_ERROR_SESSION_REMOTE_CLOSE 对方关闭了session";
        break;
    case HL_ERROR_SESSION_CLOSE_CALLED:
        msg = "HL_ERROR_SESSION_CLOSE_CALLED 我方关闭了session";
        break;
    case HL_ERROR_SESSION_CLOSE_TIMEOUT:
        msg = "HL_ERROR_SESSION_ALIVE_TIMEOUT_CLOSE 底层⼼跳超时，需要重新连接";
        break;
    case HL_ERROR_REMOTE_SITE_BUFF_FULL:
        msg = "HL_ERROR_BUFF_FULL_REMOTE 对方接收buff满了";
        break;
    case HL_ERROR_USER_LISTEN_BREAK:
        msg = "HL_ERROR_LISTEN_BREAK 主动打断监听";
        break;
    case HL_ERROR_MAX_SESSION:
        msg = "HL_ERROR_SESSION_MAX session 超出连接数";
        break;
    case HL_ERROR_USER_CONNECT_BREAK:
        msg = "HL_ERROR_CONNECT_BREAK 主动打断连接";
        break;
    case HL_ERROR_SESSION_CLOSED_INSUFFICIENT_MEMORY:
        msg = "HL_ERROR_SESSION_CLOSED_INSUFFICIENT_MEMORY 因内存资源不足,关闭连接";
        break;
    case HL_ERROR_INVALID_LICENSE:
        msg = "HL_ERROR_INVALID_LICENSE 无效LICENSE";
        break;
    case HL_ERROR_FAIL_TO_CREATE_THREAD:
        msg = "HL_ERROR_FAIL_TO_ALLOCATE_MEMORY 创建线程失败";
        break;
    case HL_ERROR_FAIL_TO_ALLOCATE_MEMORY:
        msg = "HL_ERROR_FAIL_TO_ALLOCATE_MEMORY 内存分配失败";
        break;
    case HL_ERROR_INVALID_INIT_STRING:
        msg = "HL_ERROR_INVALID_INIT_STRING 无效加密字串";
        break;
    case HL_ERROR_BUFF_FULL_LOCAL:
        msg = "HL_ERROR_BUFF_FULL_LOCAL 我方发送buff满了";
        break;
    case HL_ERROR_BUFF_LEN:
        msg = "HL_ERROR_BUFF_LEN 读取数据的上层buff太小";
        break;
    case HL_ERROR_DATA_LEN_ZERO:
        msg = "HL_ERROR_DATA_LEN_ZERO 发送的数据必须大于0";
        break;
    case HL_ERROR_DATA_MAX_2M:
        msg = "HL_ERROR_DATA_MAX_2M 发送的数据最大不超过2M";
        break;
    case HL_ERROR_DATA_INVALID:
        msg = "HL_ERROR_DATA_INVALID 无效数据";
        break;
    case HL_ERROR_NOT_AVAILABLE:
        msg = "HL_ERROR_NOT_AVAILABLE 库没有开发该功能，请联系吕工";
        break;
    case HL_ERROR_OPEN_FILE_FAIL:
        msg = "HL_ERROR_OPEN_FILE_FAIL 打开文件失败";
        break;
    case HL_ERROR_PATH_TOO_LENGTH:
        msg = "HL_ERROR_PATH_TOO_LENGTH 文件路径太长";
        break;
    case HL_ERROR_IS_P2P_CALLBACK_DATA:
        msg = "HL_ERROR_IS_P2P_CALLBACK_DATA 你开启了p2p库回调数据，不需要你去读数据";
        break;
    case HL_ERROR_IS_P2P_LISTEN_APP_CONNECT:
        msg = "HL_ERROR_IS_P2P_CALLBACK_DATA 你开启了p2p监听APP连接，不需要你去监听连接";
        break;
    case HL_ERROR_CLOUD_DOMAIN_PARSE_FAIL:
        msg = "HL_ERROR_CLOUD_DOMAIN_PARSE_FAIL 云端服务器域名解析失败";
        break;
    case HL_ERROR_CLOUD_CONNECT_SVR_FAIL:
        msg = "HL_ERROR_CLOUD_CONNECT_SVR_FAIL 云服务器连接失败";
        break;
    case HL_ERROR_CODEC_FAIL:
        msg = "HL_ERROR_CODEC_FAIL 音视频编解码拍照录像等失败";
        break;
    case HL_ERROR_CODEC_ID_INVALID:
        msg = "HL_ERROR_CODEC_ID_INVALID cid无效";
        break;
    case HL_ERROR_CODEC_NOT_I_FRAME:
        msg = "HL_ERROR_CODEC_NOT_I_FRAME 需要i帧";
        break;
    case HL_ERROR_CODEC_DEST_BUFF_TOO_SMALL:
        msg = "HL_ERROR_CODEC_DEST_BUFF_TOO_SMALL 上层保存解码或编码后数据的buff太小(1,如果是视频,会返回当前视频的最新宽高,用于上层创建更大buff的依据；2,如果是音频,那就上层自己直接创建更大一点的buff)";
        break;
    case HL_ERROR_FAIL:
        msg = "HL_ERROR_FAIL 失败,看cb_error()回调信息";
        break;
    case HL_ERROR_MAX_SESSION_REMOTE:
        msg = "HL_ERROR_MAX_SESSION_REMOTE 对方的连接数量已满,不能再连接了";
        break;
    case HL_ERROR_CREATE_SOCKET_FAIL:
        msg = "HL_ERROR_CREATE_SOCKET_FAIL 创建socket失败,需要重启一下设备恢复";
        break;
    case HL_ERROR_DID_UNSUPPORT_WEBRTC:
        msg = "HL_ERROR_MAX_SESSION_REMOTE 这个did不支持webrtc,请使用支持webrtc的did";
        break;
    }

    char buf[1024] = {0};
    va_list args;
    va_start(args, format);
    vsprintf(buf, format, args);
    va_end(args);

    struct timeval now;
    gettimeofday(&now, NULL);

    int d = now.tv_sec % (24 * 60 * 60);
    int h = d / (60 * 60) + 8;
    int m = (d % (60 * 60)) / 60;
    int ss = now.tv_sec % 60;
    printf("[%02d:%02d:%02d.%03d] ret=%d msg=%s %s\n", h, m, ss, (int)now.tv_usec / 1000, ret, buf, msg);
}

/**
 * 获取连接类型
 * **/
static char *_get_session_connect_status(int session)
{
    switch (session)
    {
    case HL_CONNECT_STATUS_ONLINE_LAN:
        return "局域网连接";
    case HL_CONNECT_STATUS_ONLINE_P2P:
        return "P2P连接";
    case HL_CONNECT_STATUS_ONLINE_DEV_RELAY:
        return "设备转发连接";
    case HL_CONNECT_STATUS_ONLINE_PLAT_RELAY:
        return "平台转发连接";
    case HL_CONNECT_STATUS_ONLINE_SVR_RELAY:
        return "服务器UDP转发连接";
    case HL_CONNECT_STATUS_ONLINE_SVR_RELAY_TCP:
        return "服务器TCP转发连接";
    }
    return "未知连接状态";
}

static int gSession = 0;
static void cb_log(char type, char *log)
{
    // _hl_log(1000, "cb_log() type=%d  log=%s", type, log);
}

//-------------下面是dev回调-------------
// 设备端建议上层重启一下设备，因为设备网络出现了问题
static void cb_dev_reboot_advise(char type)
{
    _hl_log(1000, "cb_dev_reboot_advise() type=%d", type);
}

// 设备端与p2p服务器连接状态
static void cb_dev_svr_login_status(char status)
{
    _hl_log(1000, "cb_dev_svr_login_status() status=%d", status);
}

// 回调p2p和relay域名让上层去进行域名解析
static void cb_dev_need_parse_domain(char *p2p_domains, char *relay_domains)
{
    _hl_log(1000, "cb_dev_need_parse_domain() p2p_domains=%s relay_domains=%s", p2p_domains, relay_domains);
}

// 收到直播事件
static void cb_dev_webrtc_live(int wid, char status, char channel, char *data, int data_len)
{
    _hl_log(1000, "cb_dev_webrtc_live() status=%d", status);
}

// 收到app透传数据过来
static void cb_dev_transfer_data(int aid, int seq, char *data, int data_len)
{
    _hl_log(1000, "cb_dev_transfer_data() aid=%d seq=%d data=%s data_len=%d", aid, seq, data, data_len);
}

// 连接状态改变回调
static void cb_dev_connect_status(int session, int status, char *remote_lan_ip)
{
    _hl_log(1000, "cb_dev_connect_status() session=%d status=%d remote_lan_ip=%s", session, status, remote_lan_ip);
}

// 库回调数据
static void cb_dev_session_data(int session, char channel, char *data, int data_len)
{
    _hl_log(1000, "cb_dev_session_data() session=%d channel=%d data_len=%d", session, channel, data_len);
}

// 当前连接的带宽
static void cb_dev_bandwidth(int session, int send_bw, int recv_bw)
{
    _hl_log(1000, "cb_dev_bandwidth() sess=%d send_bw=%d recv_bw=%d", session, send_bw, recv_bw);
}

/**
 * 读取指令数据的线程,类似尚云函数，和tcp方式读取一致
 * **/
static void *_thd_read_cmd_sy(void *user_data)
{
    pthread_detach(pthread_self());

    _hl_log(1000, "_thd_read_cmd_sy()....开始 session=%d", gSession);

    // 0-表示一直不超时，直到读取到数据，或关闭session; >0表示过这么长时间后，如果没有读到数据就超时，单位是毫秒
    int timeout_ms = 0;
    // 这个值要足够大，要>=发送端发送的最大包，因为和UDP读取数据一样,如果小了，多余的数据将会被丢弃
    char *data = (char *)calloc(1, 1024);
    int data_len = 0;
    int ret = 0;

    char flag = 1;
    while (flag)
    {
        memset(data, 0, 1024);
        data_len = 100; // 传入表示：要读100个字节，没有读到不会出来，直到超时或者连接断开；传出表示：读到了多少数据；超时的时候很可能会读到一部分数据
        ret = HLP2P_Read(gSession, 0, data, &data_len, timeout_ms);
        if (ret == HL_ERROR_SUCCESSFUL)
        {
            _hl_log(ret, "_thd_read_cmd_sy()....读到对方发来的数据 data=%s data_len=%d", data, data_len);
        }
        else if (ret == HL_ERROR_TIME_OUT)
        {
            _hl_log(ret, "_thd_read_cmd_sy()....读取超时，继续读就好");
        }
        // 读取失败，需要断开连接，等待app重新连接，上层关于这个session的资源都需要记得释放
        else if (ret < 0)
        {
            _hl_log(ret, "_thd_read_cmd_sy()....读取失败,已经断开连接了,需要重新再连接");
            break;
        }
    }

    free(data); // 释放分配的内存
    _hl_log(1000, "_thd_read_cmd_sy()....线程退出");
    return NULL;
}

/**
 * 从通道1读取大数据,udp方式读取
 * **/
static void *_thd_read_data_sy(void *user_data)
{
    pthread_detach(pthread_self());

    _hl_log(1000, "_thd_read_data_sy()....开始 session=%d", gSession);

    // 0-表示一直不超时，直到读取到数据，或关闭session; >0表示过这么长时间后，如果没有读到数据就超时，单位是毫秒
    int timeout_ms = 0;
    // 这个值要足够大，要>=发送端发送的最大包，因为和UDP读取数据一样,如果小了，多余的数据将会被丢弃
    char *data = (char *)calloc(1, 1025);
    int data_len = 0;
    int ret = 0, sum = 0;
    char flag = 1;
    while (flag)
    {
        memset(data, 0, 1024);
        data_len = 1024; // 传入表示：要读100个字节，没有读到不会出来，直到超时或者连接断开；传出表示：读到了多少数据；超时的时候很可能会读到一部分数据
        ret = VEEPAI_Read(gSession, 1, data, &data_len, timeout_ms);
        if (ret == HL_ERROR_SUCCESSFUL)
        {
            sum += data_len;
            _hl_log(ret, "_thd_read_data_sy()....读到对方发来的数据 sum=%d data_len=%d", sum, data_len);
        }
        else if (ret == HL_ERROR_TIME_OUT)
        {
            _hl_log(ret, "_thd_read_data_sy()....读取超时，继续读就好");
        }
        // 读取失败，需要断开连接，等待app重新连接，上层关于这个session的资源都需要记得释放
        else if (ret < 0)
        {
            _hl_log(ret, "_thd_read_data_sy()....读取失败,已经断开连接了,需要重新再连接");
            break;
        }
    }

    free(data); // 释放分配的内存
    _hl_log(1000, "_thd_read_data_sy()....线程退出");
    return NULL;
}

/**
 * 向对方发送大数据,类似尚云函数
 * **/
static void *_thd_send_data_sy(void *user_data)
{
    pthread_detach(pthread_self());

    _hl_log(1000, "_thd_send_data_sy()....开始 session=%d", gSession);

    /**
     * 1）发送大数据一般不加密，库里面采用AES对称加密，对大数据加密浪费cpu
     * 2）如果一定要加密，建议上层对数据头进行加密，或采用其他策略
     * **/
    char *data = (char *)calloc(1, 1024);
    int ret = 0, read_len = 0, write_len = 0;
    int total = 512 * 1024, sum = 0; // 要发送的数据
    char flag = 0, f = 1;
    while (sum < total && f)
    {
        // 1.检查对应通道本地缓存还有多少
        flag = 1;
        while (flag)
        {
            ret = HLP2P_Check_Buffer(gSession, 1, &write_len, &read_len);
            // 该通道底层还有很多数据没有发送出去，可能网络有点慢，需要延时一下
            if (ret == HL_ERROR_BUFF_FULL_LOCAL || (ret == HL_ERROR_SUCCESSFUL && write_len > (256 * 1024)))
                usleep(10 * 1000); /***需要延时，延时10ms**/
            else if (ret < 0)
            {
                flag = 0;
                f = 0;
                _hl_log(ret, "_thd_send_data_sy()...连接出现问题了，需要断开重连");
                break;
            }
            else
                flag = 0; // 结束延时，可以发数据了
        }

        // 2.向对方发送数据
        ret = HLP2P_Write(gSession, 1, data, 1024);
        if (ret > 0)
        {
            sum += ret;
            _hl_log(ret, "_thd_send_data_sy()...数据发送成功 %d%%", sum * 100 / total);
        }
        else if (ret == HL_ERROR_BUFF_FULL_LOCAL)
        {
            _hl_log(ret, "_thd_send_data_sy()...这一帧发送失败，因为本地缓存已经满了，两种处理方案：1）丢掉这帧，2）延时10ms再次重发这一帧 直到发送成功");
        }
        else if (ret < 0)
        {
            flag = 0;
            f = 0;
            _hl_log(ret, "_thd_send_data_sy()...连接出现问题了，需要断开重连");
        }
    }

    if (total == sum)
        _hl_log(ret, "_thd_send_data_sy()...所有数据都发送完成 total=%d sum=%d", total, sum);

    free(data); // 释放分配的内存
    _hl_log(1000, "qwe..._thd_send_data_sy()....线程退出");
    return NULL;
}

/**
 * 监听app连接
 * **/
static void *_thd_listen_app_conn_sy(void *user_data)
{
    pthread_detach(pthread_self());

    st_HLP2P_Session SInfo = {0};
    pthread_t tid;
    // 设备ID
    char *did = "HIXX00000036KPMJH";
    char *license = "CDDDQPLNMGH";

    int ret = 0;
    char *data = NULL;
    while (1)
    {
        _hl_log(1000, "qwe..._thd_listen_app_conn()....等待app来连接 did=%s",did);
        gSession = HLP2P_Listen(did, 6666, 0, HL_LISTEN_MODE_WAN, license);
        _hl_log(gSession, "qwe..._thd_listen_app_conn()....app连接过来");
        if (gSession > 0)
        {
            // 1.检查这次连接的状态类型
            memset(&SInfo, 0, sizeof(SInfo));
            ret = HLP2P_Check(gSession, &SInfo);
            _hl_log(ret, "app连接过来 连接状态=%s", _get_session_connect_status(SInfo.bMode));

            /***
             * 2.读取命令通道数据，其他通道数据自己实现，方式是一样的
             *
             * 注意：如果设置了库回调数据，则不需要读取数据
             * **/

            pthread_create(&tid, NULL, _thd_read_cmd_sy, NULL);

            // 3.读数据还没有到超时时间 想结束读数据的线程,调用该函数读的函数会马上返回,需要提前把现在循环标志设置成0
            //  HLP2P_ReadBreak(gSession,0);

            // 4.开线程从通道1读大数据
            pthread_create(&tid, NULL, _thd_read_data_sy, NULL); // 按照udp方式读取

            /**
             * 5.向对方发送数据
             * a）命令数据直接发送
             * b）大数据发送前需要调用hl_p2p_check_buffer()函数，大数据：音视频数据/文件数据/其他需要进行大量发送的数据
             * **/

            // 发送命令数据，使用0通道发送指令数据，0通道默认是加密的
            data = "dev send cmd";
            ret = HLP2P_Write(gSession, 0, data, strlen(data));
            if (ret < 0)
            {
                _hl_log(ret, "发送数据失败");
                return 0;
            }

            // 开线程发送大数据
            pthread_create(&tid, NULL, _thd_send_data_sy, NULL);

            // 6.清空库中对应通道的数据
            // HLP2P_Clear_Buffer(gSession, 0, 1);

            // 7.断开与对方的连接
            // HLP2P_Close(gSession);
            // HLP2P_ForceClose(gSession);
        }
    }

    _hl_log(1000, "qwe..._thd_listen_app_conn()....线程退出");
    return NULL;
}

//------使用类似尚云函数-------
int hl_init_sy()
{
    // 0.获取库版本号
    _hl_log(1000, "库版本号=%d", HLP2P_GetAPIVersion());

    // 1.设置回调，要在初始化库前
    hl_p2p_cb cb;
    bzero(&cb, sizeof(cb));
    cb.cb_log = &cb_log;
    // 设备端回调，当前作为app不接收这些回调
    cb.cb_dev_reboot_advise = &cb_dev_reboot_advise;
    cb.cb_dev_svr_login_status = &cb_dev_svr_login_status;
    cb.cb_dev_need_parse_domain = &cb_dev_need_parse_domain;
    cb.cb_dev_webrtc_live = &cb_dev_webrtc_live;
    cb.cb_dev_transfer_data = &cb_dev_transfer_data;
    cb.cb_dev_connect_status = &cb_dev_connect_status;
    cb.cb_dev_session_data = &cb_dev_session_data;
    cb.cb_dev_bandwidth = &cb_dev_bandwidth;
    HLP2P_SetCallback(&cb);

    // 3.开启库日志,开启会有比多日志
    HLP2P_SetLogLevel(HL_LOG_TYPE_MGT | HL_LOG_TYPE_SESS | HL_LOG_TYPE_CGT | HL_LOG_TYPE_WEBRTC, 0, 1);

    // 3.初始化库
    // 加密字串
    char *svr = "DAS-2E97A741893A5D8FF6F425C8116F5588CFA1226897E93466A75449C895E09D71C815B958DDBD7F771C9A6F0C112F7996F985834F9DD5DCCE7A7A64A86BAEF0FF21F3C04456921E7F13E961B4EEAFB8E15C27341A076DB83512880DA86BB46DF0C525211150ADFF6BA4408C7C5FEFF702"; 

    int ret = HLP2P_Initialize(svr);
    if (ret != HL_ERROR_SUCCESSFUL)
    {
        _hl_log(ret, "");
        return 0;
    }

    // 4.设置最大连接数和最大通道数
    HLP2P_SetSessionMax(255, 8);

    HLP2P_Share_Bandwidth(1);

    // 5.监听app连接
    pthread_t tid;
    pthread_create(&tid, NULL, _thd_listen_app_conn_sy, NULL);

    // 6.注销库
    // HLP2P_Initialize();

    return 1;
}
